#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright (c) 2009, Kao Cardoso Félix
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
#   notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
#   copyright notice, this list of conditions and the following
#   disclaimer in the documentation and/or other materials provided
#   with the distribution.
# * Neither the name of the python-game-tools nor the names of its
#   contributors may be used to endorse or promote products derived
#   from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.


"""Module defining undoable actions that the editor can make.

Actions are defined by means of duck typing. Every action class should
have an undo method that takes no parameters and undo any modification
done to the involved objects.

NodeEdits are actions that modify node properties. The base class
defines a common initializer and undo method.

"""
from vec2 import Vec2

class ActionStack(object):
    """A class to manage a stack of undoable actions.
    """

    def __init__(self):
        """Create the emtpy stack.
	"""
        self.actions_done = []

    def undo_last(self):
        """Undo the top action of the stack.
        """
        if self.actions_done:
            last_action = self.actions_done.pop()
            last_action.undo()

    def cancel(self, action):
        """Remove action from the stack without undoing it. This
        should be called if an action is interrupted.
        """
        self.actions_done.remove(action)

    def push(self, action):
        """Push an action to the stack.
        """
        self.actions_done.append(action)

# Module level action stack.
# Will be used as a singleton by action classes.
stack = ActionStack()


class UndoableAction(object):
    """Undoable action base class. Handle action stack pushing.
    """

    def __init__(self, ):
        """
	"""
	stack.push(self)


class NodeCreateAction(UndoableAction):
    """The action of creating a node.

    The node isn't really created on this class, just put into a
    layer.
    """

    def __init__(self, editor, mpos):
        """
	"""
        super(NodeCreateAction, self).__init__()
        node = editor.node_source.get_node()
        node.x, node.y = mpos.x, mpos.y
        editor.current_layer.nodes.append(node)

        self.node = node
        self.layer = editor.current_layer

    def undo(self):
        self.layer.remove_node(self.node)


class NodeDeleteAction(UndoableAction):
    """
    """

    def __init__(self, editor):
        """The action of deleting a node.
	"""
        super(NodeDeleteAction, self).__init__()
        self.node = editor.hover_node
        self.layer = editor.current_layer
	self.node_z = self.layer.remove_node(self.node)

        editor.hover_node = None

    def undo(self):
        """
        """
        self.layer.add_node(self.node, self.node_z)


class NodeReorder(UndoableAction):
    """Change the order of the hover node inside it's layer.
    """

    def __init__(self, editor, new_z):
        """
	"""
        super(NodeReorder, self).__init__()
        self.node =  editor.hover_node
        self.layer = editor.current_layer

	self.node_z = self.layer.remove_node(self.node)
        self.layer.add_node(self.node, new_z)

    def undo(self):
        self.layer.nodes.remove(self.node)
        self.layer.add_node(self.node, self.node_z)


###############################################################################
## Node edits classes begin here

class NodeEditAction(UndoableAction):
    """The action of editing a node.

    Any action that changes nodes properties are considered node
    edits.
    """

    def __init__(self, node):
        """Create a edit for the given node. click_pos is the position
        of the mouse click that started the edit in world coordinates.
	"""
        super(NodeEditAction, self).__init__()
	self.node = node
        self.node_state = {
            'scale' : node.scale,
            'rotation' : node.rotation,
            'position' : node.position,
            'opacity' : node.opacity,
            }

    def undo(self):
        """Undo the node edit.
        """
        self.node.scale = self.node_state['scale']
        self.node.rotation = self.node_state['rotation']
        self.node.position = self.node_state['position']
        self.node.opacity = self.node_state['opacity']


class NodeOpacityChange(NodeEditAction):
    """An action to change the node opacity
    """

    def __init__(self, node, increment):
        """Create an action to change the node opacity by increment.
	"""
        super(NodeOpacityChange, self).__init__(node)
        node.opacity += increment
        if node.opacity < 0:
            node.opacity = 0
        elif node.opacity > 255:
            node.opacity = 255


## Mouse drag node edits

class NodeMouseDragEditAction(NodeEditAction):
    """Subclass for mouse dragging edits
    """

    def __init__(self, node, click_pos):
        """Create a drag edit on node starting at click_pos
	"""
        super(NodeMouseDragEditAction, self).__init__(node)
        self.click_pos = Vec2(click_pos)


class NodeMove(NodeMouseDragEditAction):
    """An edit to move a node.
    """

    def update(self, mpos):
        """Update the edit based on the current mouse position given
        in world coordinates.
	"""
        click = self.click_pos
        npos = Vec2(self.node_state['position'])
        self.node.position = npos + mpos - click


class NodeScale(NodeMouseDragEditAction):
    """An edit to scale a node.
    """

    def update(self, mpos):
        """Update the edit based on the current mouse position given
        in world coordinates.
	"""
        node = self.node
        scale = self.node_state['scale']

        npos_to_click = self.click_pos - node.position
        npos_to_mpos = mpos - node.position
        node.scale = scale * (npos_to_mpos.length() / npos_to_click.length())


class NodeRotate(NodeMouseDragEditAction):
    """An edit to rotate a node.
    """

    def update(self, mpos):
        """Update the edit based on the current mouse position given
        in world coordinates.
	"""
        rotation = self.node_state['rotation']

        click = self.click_pos
        npos = Vec2(self.node_state['position'])

        self.node.rotation = rotation + (mpos - npos).angle(click - npos)


